package redis

import (
	"bluebell/models"
	"github.com/go-redis/redis"
	"math"
	"strconv"
	"time"
)

const (
	OneWeekInSeconds          = 7 * 24 * 3600        // 一周的秒数
	OneMonthInSeconds         = 4 * OneWeekInSeconds // 一个月的秒数
	VoteScore         float64 = 432                  // 每一票的值432分
	PostPerAge                = 20                   // 每页显示20条帖子
)


// GetPostVoteNum 根据id查询每篇帖子的投赞成票的数据
// Zset --->
// key: "bluebell:post:voted + post_id"
// value: score---> 0 1 2 member---> user_id
func GetPostVoteNum(postId int64)(vote int64,err error){
	key := KeyPostVotedZSetPrefix + strconv.Itoa(int(postId))
	// 查找key中分数是1的元素数量 -> 统计每篇帖子的赞成票的数量
	vote = client.ZCount(key,"1","1").Val()
	return vote,nil
}

// GetPostIdsInOrder 按创建时间排序 或者 按照 分数排序 (查询出的ids已经根据order从大到小排序)
// Zset --->
// key: "bluebell:post:time"或者"bluebell:post:score"
// value: score:time或者score member:post_id
func GetPostIdsInOrder(p *models.ParamPostList) ([]string,error){
	//从redis获取id
	// 1.根据用户请求中携带的order参数确定要查询的redis key
	key := KeyPostTimeZSet // 默认是时间
	if p.Order == models.OrderScore{
		key = KeyPostScoreZSet //按照分数排序
	}
	// 2.确定查询的索引起始点
	return getIdsFormKey(key,p.Page,p.Size)
}

func GetCommunityPostIdsInOrder(p *models.ParamPostList) ([]string, error){
	// 1.根据用户请求中携带的order参数确定要查询的redis key
	orderkey := KeyPostTimeZSet       // 默认是时间
	if p.Order == models.OrderScore { // 按照分数请求
		orderkey = KeyPostScoreZSet
	}

	// 使用zinterstore 把分区的帖子set与帖子分数的zset生成一个新的zset
	// 针对新的zset 按之前的逻辑取数据
	// 社区的key
	cKey := KeyCommunityPostSetPrefix + strconv.Itoa(int(p.CommunityId))
	// 利用缓存key减少zinterstore执行的次数 缓存key
	key := orderkey + strconv.Itoa(int(p.CommunityId))
	if client.Exists(key).Val() < 1{
		// 不存在，需要计算
		pipeline := client.Pipeline()
		// 将两个zset函数聚合的时候 求最大值
		pipeline.ZInterStore(key, redis.ZStore{Aggregate: "MAX"},cKey,orderkey) // zinterstore 计算
		pipeline.Expire(key, 60*time.Second) // 设置超时时间
		_, err := pipeline.Exec()
		if err != nil {
			return nil, err
		}
	}
	// 存在的就直接根据key查询ids
	return getIdsFormKey(key, p.Page, p.Size)
}

// getIDsFormKey 按照分数或者时间从大到小的顺序查询指定数量的元素
// Zset --->
// key: "bluebell:post:time"或者"bluebell:post:score"
// value: score:time或者score member:post_id
func getIdsFormKey(key string,page,size int64) ([]string,error){
	start := (page - 1) * size
	end := start + size - 1
	// 3.ZRevRange 按照分数从大到小的顺序查询指定数量的元素
	return client.ZRevRange(key,start,end).Result()
}

// GetPostVoteData 根据postIds查询每篇帖子的投赞成票的数据
// Zset --->
// key: "bluebell:post:voted + post_id"
// value: score---> 0 1 2 member---> user_id
func GetPostVoteData(postIds []string) (data []int64, err error){
	data = make([]int64, 0, len(postIds))
	for _,postId := range postIds{
		key := KeyPostVotedZSetPrefix + postId
		// 查找key中分数是1的元素数量 -> 统计每篇帖子的赞成票的数量
		v := client.ZCount(key, "1", "1").Val()
		data = append(data, v)
	}
	return data, nil
}

func CreatePost(postID, userID uint64, title, summary string, CommunityID uint64)(err error){
	now := float64(time.Now().Unix())
	votedKey := KeyPostVotedZSetPrefix + strconv.Itoa(int(postID))
	communityKey := KeyCommunityPostSetPrefix + strconv.Itoa(int(CommunityID))
	postInfo := map[string]interface{}{
		"title":    title,
		"summary":  summary,
		"post:id":  postID,
		"user:id":  userID,
		"time":     now,
		"votes":    1,
		"comments": 0,
	}

	// 事务操作
	pipeline := client.TxPipeline()
	// 投票 zSet
	pipeline.ZAdd(votedKey, redis.Z{ // 作者默认投赞成票
		Score:  1,
		Member: userID,
	})
	pipeline.Expire(votedKey, time.Second*OneMonthInSeconds*6) // 过期时间：6个月
	// 文章 hash
	pipeline.HMSet(KeyPostInfoHashPrefix+strconv.Itoa(int(postID)), postInfo)
	// 添加到分数 ZSet
	pipeline.ZAdd(KeyPostScoreZSet, redis.Z{
		Score:  now + VoteScore,
		Member: postID,
	})
	// 添加到时间 ZSet
	pipeline.ZAdd(KeyPostTimeZSet, redis.Z{
		Score:  now,
		Member: postID,
	})
	// 添加到对应版块 把帖子添加到社区 set
	pipeline.SAdd(communityKey, postID)
	_, err = pipeline.Exec()
	return
}

// VoteForPost	为帖子投票
func VoteForPost(userID string, postID string, v float64) (err error){
	// 1.判断投票限制
	// 去redis取帖子发布时间
	postTime := client.ZScore(KeyPostTimeZSet, postID).Val()
	if float64(time.Now().Unix())-postTime > OneWeekInSeconds { // 超过一个星期就不允许投票了
		// 不允许投票了
		return ErrorVoteTimeExpire
	}
	// 2、更新帖子的分数
	// 2和3 需要放到一个pipeline事务中操作
	// 判断是否已经投过票 查当前用户给当前帖子的投票记录
	key := KeyPostVotedZSetPrefix + postID
	ov := client.ZScore(key, userID).Val()

	// 更新：如果这一次投票的值和之前保存的值一致，就提示不允许重复投票
	if v == ov {
		return ErrVoteRepeated
	}
	var op float64
	if v > ov {
		op = 1
	} else {
		op = -1
	}
	//更新分数
	diffAbs := math.Abs(ov - v)                // 计算两次投票的差值
	pipeline := client.TxPipeline()            // 事务操作
	incrementScore := VoteScore * diffAbs * op // 计算分数（新增）
	_, err = pipeline.ZIncrBy(KeyPostScoreZSet, incrementScore, postID).Result() // 更新分数
	if err != nil {
		return err
	}
	// 3、记录用户为该帖子投票的数据
	if v == 0 {
		_, err = client.ZRem(key, postID).Result()
	} else {
		pipeline.ZAdd(key, redis.Z{ // 记录已投票
			Score:  v, // 赞成票还是反对票
			Member: userID,
		})
	}
	// 4、更新帖子的投票数
	pipeline.HIncrBy(KeyPostInfoHashPrefix+postID, "votes", int64(op))
	_, err = pipeline.Exec()
	return err
}